home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection 1998 Fall: Game Toolkit / Disc.iso / Samples / SprocketExamples / GlyphaIV / GlyphaIV Sources / G4Sound.c < prev    next >
Encoding:
Text File  |  1998-07-14  |  14.3 KB  |  412 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        G4Sound.c
  3.  
  4.     Contains:    xxx put contents here xxx
  5.  
  6.     Version:    xxx put version here xxx
  7.  
  8.     Copyright:    © 1998 by Apple Computer, Inc., all rights reserved.
  9.  
  10.     File Ownership:
  11.  
  12.         DRI:                xxx put dri here xxx
  13.  
  14.         Other Contact:        xxx put other contact here xxx
  15.  
  16.         Technology:            xxx put technology here xxx
  17.  
  18.     Writers:
  19.  
  20.         (sjb)    Steve Bollinger
  21.  
  22.     Change History (most recent first):
  23.  
  24.          <2>      7/1/98    sjb        Update to CWPro 2
  25. */
  26.  
  27.  
  28. //============================================================================
  29. //----------------------------------------------------------------------------
  30. //                                    Sound.c
  31. //----------------------------------------------------------------------------
  32. //============================================================================
  33.  
  34. // This file handles all sound routines.  It handles 2 concurrent sound…
  35. // channels allowing 2 sounds to be played simultaneously.  It also handles…
  36. // a system of priorites whereby you can ensure that "important" sounds don't…
  37. // get cut off by "lesser" sounds.  In that there are 2 channels however,…
  38. // "lesser" sounds are not discounted outright - both channels are considered…
  39. // to determine if one of the channels is not playing at all (priority = 0) or…
  40. // playing a sound of an even lesser priority.  Make sense?
  41.  
  42.  
  43. #include <Sound.h>
  44. #include "G4Externs.h"
  45. #include <Resources.h>
  46.  
  47. #define kMaxSounds                17            // Number of sounds to load.
  48. #define    kBaseBufferSoundID        1000        // ID of first sound (assumed sequential).
  49. #define kSoundDone                913            // Just a number I chose.
  50. #define kSoundDone2                749            // Just a number I chose.
  51.  
  52.  
  53. void PlaySound1 (short, short);
  54. void PlaySound2 (short, short);
  55. pascal void ExternalCallBack (SndChannelPtr, SndCommand *);
  56. pascal void ExternalCallBack2 (SndChannelPtr, SndCommand *);
  57. void LoadAllSounds (void);
  58. OSErr LoadBufferSounds (void);
  59. OSErr DumpBufferSounds (void);
  60. OSErr OpenSoundChannel (void);
  61. OSErr CloseSoundChannel (void);
  62.  
  63.  
  64. SndCallBackUPP        externalCallBackUPP, externalCallBackUPP2;
  65. SndChannelPtr        externalChannel, externalChannel2;
  66. Ptr                    theSoundData[kMaxSounds];
  67. short                externalPriority, externalPriority2;
  68. Boolean                channelOpen, soundOn;
  69.  
  70.  
  71. //==============================================================  Functions
  72. //--------------------------------------------------------------  PlaySound1
  73. // This function takes a sound ID and a priority, and forces that sound to 
  74. // play through channel 1 - and saves the priority globally.  As well, a 
  75. // callback command is queues up in channel 1.
  76.  
  77. void PlaySound1 (short soundID, short priority)
  78. {
  79.     SndCommand    theCommand;
  80.     OSErr        theErr;
  81.     
  82.     theCommand.cmd = flushCmd;            // Send 1st a flushCmd to clear the sound queue.
  83.     theCommand.param1 = 0;
  84.     theCommand.param2 = 0L;
  85.     theErr = SndDoImmediate(externalChannel, &theCommand);
  86.     
  87.     theCommand.cmd = quietCmd;            // Send quietCmd to stop any current sound.
  88.     theCommand.param1 = 0;
  89.     theCommand.param2 = 0L;
  90.     theErr = SndDoImmediate(externalChannel, &theCommand);
  91.     
  92.     externalPriority = priority;        // Copy priority to global variable.
  93.     
  94.     theCommand.cmd = bufferCmd;            // Then, send a bufferCmd to channel 1.
  95.     theCommand.param1 = 0;                // The sound played will be soundID.
  96.     theCommand.param2 = (long)(theSoundData[soundID]);
  97.     theErr = SndDoImmediate(externalChannel, &theCommand);
  98.     
  99.     theCommand.cmd = callBackCmd;        // Lastly, queue up a callBackCmd to notify us…
  100.     theCommand.param1 = kSoundDone;        // when the sound has finished playing.
  101.     theCommand.param2 = SetCurrentA5();
  102.     theErr = SndDoCommand(externalChannel, &theCommand, TRUE);
  103. }
  104.  
  105. //--------------------------------------------------------------  PlaySound2
  106. // This function is identical to the above function except that it handles…
  107. // playing sounds through channel 2.
  108.  
  109. void PlaySound2 (short soundID, short priority)
  110. {
  111.     SndCommand    theCommand;
  112.     OSErr        theErr;
  113.     
  114.     theCommand.cmd = flushCmd;            // Send 1st a flushCmd to clear the sound queue.
  115.     theCommand.param1 = 0;
  116.     theCommand.param2 = 0L;
  117.     theErr = SndDoImmediate(externalChannel2, &theCommand);
  118.     
  119.     theCommand.cmd = quietCmd;            // Send quietCmd to stop any current sound.
  120.     theCommand.param1 = 0;
  121.     theCommand.param2 = 0L;
  122.     theErr = SndDoImmediate(externalChannel2, &theCommand);
  123.     
  124.     externalPriority2 = priority;        // Copy priority to global variable.
  125.     
  126.     theCommand.cmd = bufferCmd;            // Then, send a bufferCmd to channel 1.
  127.     theCommand.param1 = 0;                // The sound played will be soundID.
  128.     theCommand.param2 = (long)(theSoundData[soundID]);
  129.     theErr = SndDoImmediate(externalChannel2, &theCommand);
  130.     
  131.     theCommand.cmd = callBackCmd;        // Lastly, queue up a callBackCmd to notify us…
  132.     theCommand.param1 = kSoundDone2;    // when the sound has finished playing.
  133. #if !GENERATINGCFM
  134.     theCommand.param2 = SetCurrentA5();
  135. #endif
  136.     theErr = SndDoCommand(externalChannel2, &theCommand, TRUE);
  137. }
  138.  
  139. //--------------------------------------------------------  PlayExternalSound
  140. // This function is probably poorly named for this application.  I lifted this…
  141. // whole library from one of my games and chopped it down for purposes of Glypha.
  142. // The original game treated "external" and "cockpit" sounds as seperate channels…
  143. // (such that cockpit sounds could only "override" other cockpit sounds and…
  144. // external sounds could only override other external sounds.
  145. // In any event, this is the primary function called from throughout Glypha.
  146. // This function is called with a sound ID and a priority (just some number) and…
  147. // the function then determines if one of the two sound channels is free to play…
  148. // the sound.  It determines this by way of priorities.  If a sound channel is…
  149. // idle and playing no sound, its channel priority is 0.  Since the priority of…
  150. // the sound you want to play is assumed to be greater than 0, it will, without…
  151. // a doubt, be allowed to play on an idle channel.  If however there is already…
  152. // a sound playing (the channel's priority is not equal to 0), the sound with the…
  153. // largest priority wins.  Mind you though that there are two channels to choose…
  154. // between.  Therefore, the function compares the priority passed in with the…
  155. // sound channel with the lowest priority.
  156.  
  157. void PlayExternalSound (short soundID, short priority)
  158. {                            // A little error-checking.
  159.     if ((soundID >= 0) && (soundID < kMaxSounds))
  160.     {
  161.         if (soundOn)        // More error-checking.
  162.         {                    // Find channel with lowest priority.
  163.             if (externalPriority < externalPriority2)
  164.             {                // Compare priority with that of channel 1.
  165.                 if (priority >= externalPriority)
  166.                     PlaySound1(soundID, priority);
  167.             }
  168.             else
  169.             {                // Compare priority with that of channel 2.
  170.                 if (priority >= externalPriority2)
  171.                     PlaySound2(soundID, priority);
  172.             }
  173.         }
  174.     }
  175. }
  176.  
  177. //--------------------------------------------------------  ExternalCallBack
  178. // Callback routine.  If this looks ugly, blame Apple's Universal Headers.
  179. // The callback routine is called after a sound finishes playing.  The…
  180. // callback routine is extremely useful in that it enables us to know when…
  181. // to set the sound channels priority back to 0 (meaning no sound playing).
  182. // Keep in mind (by the way) that this funciton is called at interrupt time…
  183. // and thus may not cause memory to be moved.  Also, note that also because…
  184. // of the interupt situation, we need to handle setting A5 to point to our…
  185. // app's A5 and then set it back again.
  186.  
  187. #if GENERATINGCFM
  188. RoutineDescriptor ExternalCallBackRD = 
  189.         BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack);
  190. #endif
  191.  
  192. pascal void ExternalCallBack (SndChannelPtr theChannel, SndCommand *theCommand)
  193. {
  194. #if !GENERATINGCFM
  195.     long        thisA5, gameA5;
  196. #endif
  197.     
  198.     if (theCommand->param1 == kSoundDone)    // See if it's OUR callback.
  199.     {
  200. #if !GENERATINGCFM
  201.         gameA5 = theCommand->param2;            // Extract our A5 from sound command.
  202.         thisA5 = SetA5(gameA5);                // Point A5 to our app (save off current A5).
  203. #endif
  204.         
  205.         externalPriority = 0;                // Set global to reflect no sound playing.
  206.         
  207. #if !GENERATINGCFM
  208.         thisA5 = SetA5(thisA5);                // Restire A5.
  209. #endif
  210.     }
  211. }
  212.  
  213. //--------------------------------------------------------  ExternalCallBack2
  214. // This function is identical to the above function but handles sound channel 2.
  215.  
  216. #if GENERATINGCFM
  217. RoutineDescriptor ExternalCallBackRD2 = 
  218.         BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack2);
  219. #endif
  220.  
  221. pascal void ExternalCallBack2 (SndChannelPtr theChannel, SndCommand *theCommand)
  222. {
  223. #if !GENERATINGCFM
  224.     long        thisA5, gameA5;
  225. #endif
  226.     
  227.     if (theCommand->param1 == kSoundDone2)    // See if it's OUR callback.
  228.     {
  229. #if !GENERATINGCFM
  230.         gameA5 = theCommand->param2;            // Extract our A5 from sound command.
  231.         thisA5 = SetA5(gameA5);                // Point A5 to our app (save off current A5).
  232. #endif
  233.         
  234.         externalPriority2 = 0;                // Set global to reflect no sound playing.
  235.         
  236. #if !GENERATINGCFM
  237.         thisA5 = SetA5(thisA5);                // Restire A5.
  238. #endif
  239.     }
  240. }
  241.  
  242. //--------------------------------------------------------  LoadBufferSounds
  243. // This function loads up all the sounds we'll need in the game and then…
  244. // strips off their header so that we can pass them as buffer commands.
  245. // Sounds are stored in our resource fork as 'snd ' resources.  There is a…
  246. // 20 byte header that we need to remove in order to use bufferCmd's.
  247. // This function is called only once, when the game loads up.
  248.  
  249. OSErr LoadBufferSounds (void)
  250. {
  251.     Handle        theSound;
  252.     long        soundDataSize;
  253.     OSErr        theErr;
  254.     short        i;
  255.     
  256.     theErr = noErr;                        // Assume no errors.
  257.     
  258.     for (i = 0; i < kMaxSounds; i++)    // Walk through all sounds.
  259.     {                                    // Load 'snd ' from resource.
  260.         theSound = GetResource('snd ', i + kBaseBufferSoundID);
  261.         if (theSound == 0L)                // Make sure it loaded okay.
  262.             return (ResError());        // Return reason it failed (if it did).
  263.         
  264.         HLock(theSound);                // If we got this far, lock sound down.
  265.                                         // Calculate size of sound minus header.
  266.         soundDataSize = GetHandleSize(theSound) - 20L;
  267.         HUnlock(theSound);                // Okay, unlock.
  268.                                         // Create pointer the size calculated above.
  269.         theSoundData[i] = NewPtr(soundDataSize);
  270.         if (theSoundData[i] == 0L)        // See if we created it okay.
  271.             return (MemError());        // If failed, return the reason why.
  272.         HLock(theSound);                // Okay, lock the sound handle again.
  273.                                         // Copy sound data (minus header) to our pointer.
  274.         BlockMove((Ptr)(*theSound + 20L), theSoundData[i], soundDataSize);
  275.         HUnlock(theSound);                // Unlock sound handle again.
  276.         ReleaseResource(theSound);        // And toss it from memory.
  277.     }
  278.     
  279.     return (theErr);
  280. }
  281.  
  282. //--------------------------------------------------------  DumpBufferSounds
  283. // This function is called when Glypha exits (quits).  All those nasty pointers…
  284. // we created in the above function are reclaimed.
  285.  
  286. OSErr DumpBufferSounds (void)
  287. {
  288.     OSErr        theErr;
  289.     short        i;
  290.     
  291.     theErr = noErr;
  292.     
  293.     for (i = 0; i < kMaxSounds; i++)        // Go through all sound pointers.
  294.     {
  295.         if (theSoundData[i] != 0L)            // Make sure it exists.
  296.             DisposePtr(theSoundData[i]);        // Dispose of it.
  297.         theSoundData[i] = 0L;                // Make sure it reflects its "nonexistence".
  298.     }
  299.     
  300.     return (theErr);
  301. }
  302.  
  303. //--------------------------------------------------------  OpenSoundChannel
  304. // This should perhaps be called OpenSoundChannels() since it opens two.
  305. // It is called once (at initialization) to set up the two sound channels…
  306. // we will use throughout Glypha.  For purposes of speed, 8-bit sound channels…
  307. // with no interpolation and monophonic are opened.  They'll use the sampled…
  308. // synthesizer (digitized sound) and be assigned their respective callback…
  309. // routines.
  310.  
  311. OSErr OpenSoundChannel (void)
  312. {
  313.     OSErr        theErr;
  314.     
  315.     #if GENERATINGCFM
  316.         externalCallBackUPP = &ExternalCallBackRD;    // Handle Universal Header ugliness.
  317.         externalCallBackUPP2 = &ExternalCallBackRD2;
  318.     #else
  319.         externalCallBackUPP = (SndCallBackUPP) &ExternalCallBack;
  320.         externalCallBackUPP2 = (SndCallBackUPP) &ExternalCallBack2;
  321.     #endif
  322.     
  323.     theErr = noErr;                                    // Assume no errors.
  324.     
  325.     if (channelOpen)                                // Error checking.
  326.         return (theErr);
  327.     
  328.     externalChannel = 0L;
  329.     theErr = SndNewChannel(&externalChannel,         // Open channel 1.
  330.             sampledSynth, initNoInterp + initMono, 
  331.             (SndCallBackUPP)externalCallBackUPP);
  332.     if (theErr == noErr)                            // See if it worked.
  333.         channelOpen = TRUE;
  334.     
  335.     externalChannel2 = 0L;
  336.     theErr = SndNewChannel(&externalChannel2,         // Open channel 2.
  337.             sampledSynth, initNoInterp + initMono, 
  338.             (SndCallBackUPP)externalCallBackUPP2);
  339.     if (theErr == noErr)                            // See if it worked.
  340.         channelOpen = TRUE;
  341.     
  342.     return (theErr);
  343. }
  344.  
  345. //--------------------------------------------------------  CloseSoundChannel
  346. // This function is called only upon quitting Glypha.  Both sound channels…
  347. // we created above are closed down.
  348.  
  349. OSErr CloseSoundChannel (void)
  350. {
  351.     OSErr        theErr;
  352.     
  353.     theErr = noErr;
  354.     
  355.     if (!channelOpen)            // Error checking.
  356.         return (theErr);
  357.     
  358.     if (externalChannel != 0L)    // Dispose of channel 1 (if open).
  359.         theErr = SndDisposeChannel(externalChannel, TRUE);
  360.     externalChannel = 0L;        // Flag it closed.
  361.     
  362.     if (externalChannel2 != 0L)    // Dispose of channel 2 (if open).
  363.         theErr = SndDisposeChannel(externalChannel2, TRUE);
  364.     externalChannel2 = 0L;        // Flag it closed.
  365.     
  366.     if (theErr == noErr)
  367.         channelOpen = FALSE;
  368.     
  369.     return (theErr);
  370. }
  371.  
  372. //--------------------------------------------------------  InitSound
  373. // All the above initialization routines are handled by this one function.
  374. // This single function is the only one that needs to be called - it handles…
  375. // calling the functions that load the sounds and create the sound channels.
  376. // It is called from main() when Glypha is loading up and going through its…
  377. // initialization phase.
  378.  
  379. void InitSound (void)
  380. {
  381.     OSErr        theErr;
  382.     
  383.     soundOn = TRUE;            // Note that initialization of sounds has occurred…
  384.                             // (or rather is just about to this instant!).
  385.     externalChannel = 0L;    // Flag channels as nonexistant.
  386.     externalChannel2 = 0L;
  387.     externalPriority = 0;    // Set priorities to 0 (no sound playing).
  388.     externalPriority2 = 0;
  389.                             // Load up all sounds (see above function).
  390.     theErr = LoadBufferSounds();
  391.     if (theErr != noErr)    // If it fails, we'll quit Glypha.
  392.         RedAlert("\pFailed Loading Sounds");
  393.                             // Open up the two sound channels.
  394.     theErr = OpenSoundChannel();
  395.     if (theErr != noErr)    // If that fails we'll quit Glypha as well.
  396.         RedAlert("\pFailed To Open Sound Channels");
  397. }
  398.  
  399. //--------------------------------------------------------  KillSound
  400. // Complementary to the above function, this one is called only when Glypha…
  401. // quits and it handles all the "shut-down" routines.  It also is called from…
  402. // main(), but it is called last - just as Glypha is quitting.
  403.  
  404. void KillSound (void)
  405. {
  406.     OSErr        theErr;
  407.     
  408.     theErr = DumpBufferSounds();    // Kill all sound pointers.
  409.     theErr = CloseSoundChannel();    // Close down the sound channels.
  410. }
  411.  
  412.